// 享元模式用于减少应用程序所需对象的数量，是通过将对象的内部状态划分为内在数据（instrinsic data）和外在数据（extrinsic data）两类而实现的。
// 示例：汽车登记
// 假设要开发一个系统，用以代表一个城市的所有汽车。你需要保存每一辆汽车的详细情况（品牌、型号和出厂日期）及其所有权的详细信息（车主姓名、车牌号和最近登记日期）。
// 当然，你决定把每辆汽车表示为一个对象
/* Car class */
var Car = function(make, model, year, owner, tag, renewDate) {
	this.make = make;
	this.model = model;
	this.year = year;
	this.owner = owner;
	this.tag = tag;
	this.renewDate = renewDate;
};
Car.prototype = {
	getMake: function() {
		return this.make;
	},
	getModel: function() {
		return this.model;
	},
	getYear: function() {
		return this.year;
	},
	transferOwnership: function(newOwner, newTag, newRenewDate) {
		this.owner = newOwner;
		this.tag = newTag;
		this.renewDate = newRenewDate;
	},
	renewRegistration: function(newRenewDate) {
		this.renewDate = newRenewDate;
	},
	isRegistrationCurrent: function() {
		var today = new Date();
		return today.getTime() < Date.parse(this.renewDate);
	}
};
// 这个系统最初表现不错，但随着城市人口增加，你发现它一天天变慢了。数以十万计的汽车对象耗尽了可用的计算资源。
// 可用采用享元模式减少所需对象的数目
var Car = function(make, model, year, owner, tag, renewDate) {
	this.make = make;
	this.model = model;
	this.year = year;
};
Car.prototype = {
	getMake: function() {
		return this.make;
	},
	getModel: function() {
		return this.model;
	},
	getYear: function() {
		return this.year;
	}
};
// 上面的代码删除了所有外在数据，所有处理登记事宜的方法都被转移到一个管理器对象中
// 现在对象的数据已被分为两大部分，所以必须用工厂来实例化它。
/* Car Factory */
var CarFactory = (function() {
	var createdCars = {};

	return {
		createCar: function(make, model, year) {
			if (createdCars[make + '-' + model + '-' + year]) {
				return createdCars[make + '-' + model + '-' + year];
			} else {
				var car = new Car(make, model, year);
				createdCars[make + '-' + model + '-' + year] = car;
				return car;
			}
		}
	};
})();
// 要完成这种优化还需要一个对象，那些从Car对象中删除的数据必须有个保存地点，我们用一个单体来做封装这些数据的管理器。
// Car对象与车主数据的组合称为汽车记录（car record）
/* CarRecordManager singleton */
var CarRecordManager = (function() {

	var carRecordDatabase = {};

	return {
		addCarRecord: function(make, model, year, owner, tag, renewDate) {
			var car = CarFactory.createCar(make, model, year);
			carRecordDatabase[tag] = {
				owner: owner,
				renewDate: renewDate,
				car: car
			}
		},
		transferOwnership: function(tag, newOwner, newTag, newRenewDate) {
			var record = carRecordDatabase[tag];
			record.owner = newOwner;
			record.tag = newTag;
			record.renewDate = newRenewDate;
		},
		renewRegistration: function(tag, newRenewDate) {
			carRecordDatabase[tag].renewDate = newRenewDate;
		},
		isRegistrationCurrent: function(tag) {
			var today = new Date();
			return today.getTime() < Date.parse(carRecordDatabase[tag].renewDate);
		}
	}

})();
// 这种优化是以复杂性为代价的。原先只有一个类，现在变成了一个类和两个单体对象。

// 示例：Web日历，未优化的、未使用享元模式的版本
/* CalendarItem interface */
var CalendarItem = new Interface('CalendarItem', ['display']);
/* CalandarYear class, a composite. */
var CalendarYear = function(year, parent) {
	this.year = year;
	this.element = document.createElement('div');
	this.element.style.display = 'none';
	parent.appendChild(this.element);
	function isLeapYear(y) {
		return (y>0) && !(y%4) && ((y%100) || !(y%400));
	}

	this.months = [];
	// The number of days in each month.
	this.numDays = [31, isLeapYear(this.year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
	for (var i=0, len=12; i<len; i++) {
		this.months[i] = new CalenarMonth(i, this.numDays[i], this.element);
	}
};
CalendarYear.prototype = {
	display: function() {
		for (var i=0, len=this.months.length; i<len; i++) {
			this.months[i].display();
		}
		this.element.style.display = 'block';
	}
}

/* CalendarMonth class, a composite. */
var CalendarMonth = function(monthNum, numDays, parent) {
	this.monthNum = monthNum;
	this.element = document.createElement('div');
	this.element.style.display = 'none';
	parent.appendChild(this.element);

	this.days = [];
	for (var i=0, len=numDays; i<len; i++) {
		this.days[i] = new CalendarDay(i, this.element);
	}
};
CalendarMonth.prototype = {
	display: function() {
		for (var i=0, len=this.days; i<len; i++) {
			this.days[i].display();
		}
	}
}
